/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved.
 *
 */

import path from 'path';

import { cloneDeep } from '@baize/wdk';
import { FileSet, Input, Inputs } from '@ohos/hvigor';
import { PkgContextInfoOpt } from '@ohos/hvigor-arkts-compose';
import fse from 'fs-extra';

import { projectOhosConfigManager } from '../common/global/project-ohos-config-manager.js';
import { BuildDirConst } from '../const/build-directory-const.js';
import { BundleType, DefaultTargetConst } from '../const/common-const.js';
import { ProjectModelImpl } from '../model/project/project-model-impl.js';
import { AppOpt } from '../options/build/app-ohos-config.js';
import { AppJson } from '../options/configure/app-json-options.js';
import { Dependency, DependencyType } from '../project/dependency/core/dependency-interface.js';
import { DependencyManager, getEntryMainFilePath } from '../project/dependency/dependency-manager.js';
import { CmakeUtil } from '../utils/cmake-util.js';
import { toUnixPath } from '../utils/copy-resources-util.js';
import { FileUtil } from '../utils/file-util.js';
import { HarTargetUtil } from '../utils/har-target-util.js';
import { InjectUtil } from '../utils/inject-util.js';
import { OhosLogger } from '../utils/log/ohos-logger.js';
import { getOhosTestIntermediatesRelativePath, isTemplatesAfterSourceCodeMigration } from '../utils/ohos-test-util.js';
import { pathParamsOpt } from '../validate/dependency-tag-validate.js';
import { ProcessLibsCommonProperties } from './common-properties/process-libs-common-properties.js';
import { TaskNames } from './common/task-names.js';
import { TargetTaskService } from './service/target-task-service.js';
import { OhPackageJsonOpt } from './task/ohos-har-task.js';

import AppOptObj = AppJson.AppOptObj;

// 多位版本号so正则匹配规则  如xxx.so.1.2.3
export const soLibsPattern = /^.*\.so(\.[0-9])*$/;

export class GeneratePkgContextInfo extends ProcessLibsCommonProperties {
  private readonly pkgContextInfoMap: Map<string, PkgContextInfoOpt> = new Map<string, PkgContextInfoOpt>();

  protected _log: OhosLogger = OhosLogger.getLogger(GeneratePkgContextInfo.name);
  private readonly currentBundleName: string;
  private readonly currentBundleType: string;
  private readonly pkgInfoPath: string;

  @Input() private get useNormalizedOHMUrl() {
    return !!this.targetService.getBuildOption()?.strictMode?.useNormalizedOHMUrl;
  }

  declareOutputFiles(): FileSet {
    return new FileSet().addEntry(this.pkgInfoPath, { isDirectory: false });
  }

  declareInputs() {
    const map = super.declareInputs();
    map.set('pkgContextInfoMap', JSON.stringify(Object.fromEntries(this.pkgContextInfoMap)));
    return map;
  }

  constructor(taskService: TargetTaskService) {
    super(taskService, TaskNames.Task.GENERATE_PKG_CONTEXT_INFO);
    const appOpt: AppOptObj = (this.projectModel as ProjectModelImpl).getAppRes().getAppResOpt();
    const appOptFromConfig: AppOpt = cloneDeep(projectOhosConfigManager.getOverrides()?.appOpt);
    const currentProduct = this.targetData.getProduct();
    this.currentBundleName = appOptFromConfig?.bundleName ?? this.targetData.getProduct().bundleName ?? this.projectModel.getDefaultBundleName();
    this.currentBundleType = appOptFromConfig?.bundleType ?? currentProduct.bundleType ?? appOpt.app.bundleType ?? BundleType.APP;
    this.pkgInfoPath = this.pathInfo.getIntermediatesPkgContextInfoPath();
    if (!fse.existsSync(path.dirname(this.pkgInfoPath))) {
      fse.mkdirSync(path.dirname(this.pkgInfoPath), { recursive: true });
    }
  }

  async beforeAlwaysAction(): Promise<void> {
    await super.beforeAlwaysAction();
    this.initPkgContextInfo();
  }

  beforeTask() {
    if (fse.existsSync(this.pkgInfoPath)) {
      fse.removeSync(this.pkgInfoPath);
    }
  }

  taskShouldDo(): boolean {
    return this.useNormalizedOHMUrl;
  }

  protected doTaskAction() {
    const pkgContextInfoStr = JSON.stringify(Object.fromEntries(this.pkgContextInfoMap));
    fse.writeFileSync(this.pkgInfoPath, pkgContextInfoStr);
  }

  private initPkgContextInfo() {
    const dependencyManager = new DependencyManager(this.projectModel.isFaMode(), this.moduleModel, this.projectModel);
    let { npmDependencies } = dependencyManager.collectAllDependenciesForPkgInfo();
    // 如果当前是预览或者localTest，需要收集所有依赖，后面在generate-loader-json中需要写入
    if (InjectUtil.isLocalTest() || InjectUtil.isPreviewProcess()) {
      const hspDependencies = dependencyManager.collectAllDependencies().npmDependencies
        .filter(dependency => {
          return dependency.getDependencyType() === DependencyType.DEPENDENCY_TYPE_HSP;
        });
      npmDependencies = npmDependencies.concat(hspDependencies);
    }
    for (const dependency of npmDependencies) {
      this.pkgContextInfoMap.set(dependency.getPackageName(), this.getDependenciesPkgContextInfo(dependency));
    }
    this.pkgContextInfoMap.set(this.packageJsonObj.name, this.getCurrentPkgContextInfo());
    if (this.targetName === 'ohosTest' && InjectUtil.isOhosTestCoverage()) {
      // 测试框架覆盖率添加1个 [module]_test 的语境信息，因为sdk读取的是 entryPackageName， entryPackageName 添加了 _test
      this.pkgContextInfoMap.set(`${this.packageJsonObj.name}_test`, this.getCurrentPkgContextInfo());
    }
    this.initLibsSoPkgContextInfo();
  }

  /**
   * 获取当前编译模块的信息表
   * @private
   */
  private getCurrentPkgContextInfo() {
    return {
      packageName: this.packageJsonObj.name,
      bundleName: this.getCurrentModuleBundleName(),
      moduleName: '',
      version: this.moduleModel.isHarModule() ? this.packageJsonObj.version : '',
      entryPath: this.getCurrentEntryPath(this.moduleModel.getProjectDir(), this.packageJsonObj as OhPackageJsonOpt),
      isSO: false,
      dependencyAlias: '',
    };
  }

  /**
   * 不同场景下获取当前模块的entryPath
   *
   * @param _dependencyRootPath
   * @param _pkgJsonObj
   * @private
   */
  private getCurrentEntryPath(_dependencyRootPath: string, _pkgJsonObj: OhPackageJsonOpt) {
    if (this.moduleModel.isHapModule() || this.targetName === DefaultTargetConst.OHOS_TEST_TARGET) {
      const sourceRoot = this.moduleModel.getSourceRootByTargetName(this.targetName);

      if (this.targetName === DefaultTargetConst.OHOS_TEST_TARGET && isTemplatesAfterSourceCodeMigration(sourceRoot)) {
        const ohosTest = path.join(getOhosTestIntermediatesRelativePath(), 'src/ohosTest');
        return `${toUnixPath(ohosTest)}/`;
      }
      return sourceRoot ? `${toUnixPath(path.relative(_dependencyRootPath, sourceRoot))}/` : '';
    }
    if (this.moduleModel.isHarModule()) {
      const entryMainPath = getEntryMainFilePath(_dependencyRootPath, _pkgJsonObj);
      return entryMainPath ? toUnixPath(path.relative(_dependencyRootPath, path.resolve(_dependencyRootPath, entryMainPath))) : '';
    }
    return '';
  }

  /**
   * 获取依赖收集的数据组合出的信息表
   *
   * @param dependencyInfo
   * @private
   */
  private getDependenciesPkgContextInfo(dependencyInfo: Dependency): PkgContextInfoOpt {
    const dependencyType: DependencyType = dependencyInfo.getDependencyType();
    return {
      packageName: dependencyInfo.getPackageName(),
      bundleName: this.getBundleName(dependencyInfo, dependencyType),
      moduleName: dependencyType === DependencyType.DEPENDENCY_TYPE_HSP ? dependencyInfo.getModuleJsonObj()?.module.name ?? '' : '',
      version: this.getVersion(dependencyInfo, dependencyType),

      // 声明文件需要去掉后缀，否则变量动态import场景会运行时报错
      entryPath: (dependencyInfo.getDependencyMainFileRelativePath() ?? '').replace(/\.d\.e?ts$/, ''),
      isSO: dependencyType === DependencyType.DEPENDENCY_TYPE_SO,
      dependencyAlias: this.getDepedencyAlias(dependencyInfo),
    };
  }

  /**
   * 检查给定的bundleType是否为SHARED或APP_PLUGIN，hsp则均需要回填bundleName
   * @return 如果bundletype是SHARED或APP_PLUGIN，则返回true，否则返回false
   * @param bundleType
   */
  private isBundleTypeSharedOrAppPlugin(bundleType: string){
    return bundleType === BundleType.SHARED || bundleType === BundleType.APP_PLUGIN;
  }

  /**
   * 应用间HspA 依赖应用间HspB 用HspB的bundle name
   * 应用间HspA 依赖HarB 用HspA的bundle name
   * 应用内Hsp 依赖应用内的包 不写入bundle name
   * 应用内Hsp 依赖应用间的hsp 如果bundle name不同则写入 否则不写入
   *
   * @private
   * @param dependencyInfo  依赖的包信息
   * @param dependencyType  依赖的包类型
   */
  private getBundleName(dependencyInfo: Dependency, dependencyType: DependencyType) {
    const moduleJsonObj = dependencyInfo.getModuleJsonObj();

    // 当前工程bundleType不为shared且不为appPlugin时,应用间的远程hsp的bundleName与当前工程的bundleName不同时才需要写入bundleName
    if (!this.isBundleTypeSharedOrAppPlugin(this.currentBundleType)) {
      return moduleJsonObj && this.isHspInCommonApp(moduleJsonObj, dependencyType) && moduleJsonObj.app?.bundleName !== this.currentBundleName
        ? moduleJsonObj.app?.bundleName
        : '';
    }

    // 当前工程bundleType为shared,除去hsp以外的其他情况,都写入当前工程的bundleName
    if (dependencyType !== DependencyType.DEPENDENCY_TYPE_HSP) {
      return this.currentBundleName;
    }

    // 当前工程bundleType为shared
    // 如果是本地hsp包,直接返回当前工程的bundleName
    if (DependencyManager.isLocalDependency(dependencyInfo, this.moduleModelNodePaths)) {
      return this.currentBundleName;

      // 如果是远程的应用间hsp包,则使用hsp包中的bundleName
    } else if (this.isHspInCommonApp(moduleJsonObj, dependencyType) && moduleJsonObj) {
      return moduleJsonObj.app?.bundleName ?? '';

      // 如果是远程的应用内hsp包,则报错不允许引用
    } else {
      this._log.printErrorExit('REMOTE_HSP_NOT_ALLOWED', [dependencyInfo.getPackageName()], [[dependencyInfo.getPackageName()]]);
      return '';
    }
  }

  /**
   * 定制化获取version 只有har和ohpm包需要带version
   *
   * @private
   * @param dependencyInfo  依赖的包信息
   * @param dependencyType  依赖的包类型
   */
  private getVersion(dependencyInfo: Dependency, dependencyType: DependencyType) {
    if (dependencyType === DependencyType.DEPENDENCY_TYPE_HAR || dependencyType === DependencyType.DEPENDENCY_TYPE_OTHER) {
      const pathParamsObj: pathParamsOpt = {
        parameterFilePath: this.service.getProjectModel().getParameterFileAbsolutePath(),
        ohPackageFilePath: this.isOhpmProject ? this.moduleModel.getOhPackageJson5Path() : this.moduleModel.getPackageJsonPath(),
      };
      return FileUtil.extracted(dependencyInfo.getDependencyVersion(), this.moduleModel.getParentProject().getParameterFileObj(), pathParamsObj);
    }
    return '';
  }

  /**
   * 获取别名，如果别名和pkgName不同则写入信息表
   *
   * @param dependencyInfo
   * @private
   */
  private getDepedencyAlias(dependencyInfo: Dependency) {
    return dependencyInfo.getDependencyName() === dependencyInfo.getPackageName() ? '' : dependencyInfo.getDependencyName();
  }

  /**
   * 判断依赖的hsp是否为应用间hsp,此出因为插件包的需求，插件包在构建的视角也为应用间hsp，所以此处bundleType为shared和appPlugin均为应用间hsp
   *
   * @private
   * @param moduleJsonObj
   * @param dependencyType
   */
  private isHspInCommonApp(moduleJsonObj: any, dependencyType: DependencyType) {
    return dependencyType === DependencyType.DEPENDENCY_TYPE_HSP && this.isBundleTypeSharedOrAppPlugin(moduleJsonObj.app?.bundleType);
  }

  /**
   * 由于无法通过挂载processLibs任务去收集so，所以手动进行so的收集
   *
   * @private
   */
  private initLibsSoPkgContextInfo() {
    // 通过cmake方法获取当前模块的so文件
    this.getNativeLibrary(this.targetService);

    const localHarTargets = HarTargetUtil.calDepHarTargets(this.targetService);
    for (const [moduleName, targetName] of localHarTargets) {
      const targetService = this.projectModel.getTarget(moduleName, targetName);
      if (targetService) {
        // 通过cmake方法获取当前模块所依赖的本地har的so文件
        this.getNativeLibrary(targetService);

        // 通过读取本地har的libs目录收集so文件
        this.getNativeLibraryByLibs(path.resolve(targetService.getModuleService().getModuleModel().getProjectDir(), BuildDirConst.LIBS));
      }
    }

    // 通过读取当前模块的libs目录收集so文件
    this.getNativeLibraryByLibs(path.resolve(this.moduleModel.getProjectDir(), BuildDirConst.LIBS));
    this.service
      .getHarDependencies()
      .filter((har) => !har.isLocal())
      .forEach((har) => {
        // 通过读取远程har的libs目录收集so文件
        this.getNativeLibraryByLibs(har.getDefaultLibsDir());
      });
  }

  /**
   * 循环遍历libs目录收集所有so文件
   *
   * @param libsPath
   * @private
   */
  private getNativeLibraryByLibs(libsPath: string): void {
    this.getLibsInfo(libsPath)
      .map((resolvedPath) => {
        return {
          name: path.basename(resolvedPath),
          path: path.resolve(libsPath, resolvedPath),
        };
      })
      .filter((file) => fse.lstatSync(file.path).isFile())
      .forEach((soFileInfo) => {
        this.setPkgContextInfoMapWithSo(soFileInfo.name);
      });
  }

  /**
   * 通过targetService获取当前模块需要编译的so文件
   *
   * @param targetService
   * @private
   */
  private getNativeLibrary(targetService: TargetTaskService) {
    const ninjaWorkDir = targetService.getTargetData().getPathInfo().getNinjaWorkDir();
    const nativeOption = targetService.getBuildOption().externalNativeOptions;

    // 判断当前工程是否需要进行so编译
    if (!(targetService.getShouldBuildCmake() && this.useNormalizedOHMUrl)) {
      return;
    }
    const abiFilters = CmakeUtil.checkAbiFilters(
      nativeOption?.abiFilters,
      targetService.getTargetData().isHarmonyOS(),
      targetService.getModuleService().getModuleModel(),
      targetService.getTargetData().getTargetName(),
    );
    for (const abiFilter of abiFilters) {
      const cxxAbi = path.resolve(ninjaWorkDir, abiFilter);
      const nativeLibraries = CmakeUtil.parseLibraries(cxxAbi, this.targetName, abiFilter);
      nativeLibraries.forEach((item) => {
        const soFileName = item.getNameOnDisk();
        if (soFileName) {
          this.setPkgContextInfoMapWithSo(soFileName);
        }
      });
    }
  }

  /**
   * 将so相关文件写入信息表（如果配置了collectAllLibs则还会额外收集libs目录下的其他文件）
   *
   * @param soFileName
   * @private
   */
  private setPkgContextInfoMapWithSo(soFileName: string) {
    const bundleName = this.getCurrentModuleBundleName();
    if (!this.pkgContextInfoMap.has(soFileName)) {
      this.pkgContextInfoMap.set(soFileName, getSoDefaultContextInfo(soFileName, bundleName));
    }
  }

  /**
   * 获取当前模块的bundleName， 应用间hsp的场景需要获取获取编译hsp时的bundleName
   *
   * @private
   */
  private getCurrentModuleBundleName() {
    return this.moduleModel.isHspModule() && (this.isBundleTypeSharedOrAppPlugin(this.currentBundleType)) ? this.currentBundleName : '';
  }

  initTaskDepends() {
    // no depends
  }
}

export function getSoDefaultContextInfo(soFileName: string, currentBundleName?: string): PkgContextInfoOpt {
  return {
    packageName: soFileName,
    bundleName: currentBundleName ?? '',
    moduleName: '',
    version: '',
    entryPath: '',
    isSO: true,
    dependencyAlias: '',
  };
}
